Things snap up to ledges when sectors move

When a sector moves, such as a lift activating or a door opening, things below ledges may instantly snap up to rest on top of those ledges. This affects items and monsters alike and typically manifests innocuously as a visual error, carrying no significant effect on gameplay, however it can also make items impossible to reach and cause enemies to become stuck on ledges or in ceilings. This is caused by the moving sector having an invisible bounding area made of blockmap tiles on and around it where things are updated when the sector moves.

This behavior depends on the source port and complevel. For example, Boom compatibility reduces the scope of the bug to only affect things intersecting the moving sector, rather than having the Doom v1.9 behavior of affecting all things that can be found in the sector's bounding area, regardless of actual contact with the sector.

When creating maps, this bug can always be avoided by making sure the colliding box of a thing does not intersect the wall of an upper ledge, or if it must, by flagging the intersecting linedefs as impassable or monster-blocking. More liberty can be taken, however, as this bug only manifests when a moving sector, thing, ledge, and the blockmap are combined in a particular way, as the technical explanation shows.

Technical explanation
While thing spawning is handled by the functions and, the only relevant information from them is that a newly spawned thing recognizes the floor under its center point as the floor it belongs to, and that there is no checking for walls or ledges during this stage.

The moving sector mechanics are found in the source code file. A moving sector has a bounding box (or ) consisting of blockmap tiles such that the sector is fully enveloped, and while that sector is moving, the function will iterate through all these bounding box tiles and update all things within.

...   for (x=sector->blockbox[BOXLEFT] ; x<= sector->blockbox[BOXRIGHT] ; x++) for (y=sector->blockbox[BOXBOTTOM];y<= sector->blockbox[BOXTOP] ; y++) P_BlockThingsIterator (x, y, PIT_ChangeSector); ... The function is called with each thing in these tiles to manage crushing and moving. As part of this task, it calls which updates the thing's vertical position to match the height of the floor it is on.

...   boolean		onfloor; onfloor = (thing->z == thing->floorz); P_CheckPosition (thing, thing->x, thing->y); // what about stranding a monster partially off an edge? thing->floorz = tmfloorz; thing->ceilingz = tmceilingz; if (onfloor) {       // walking monsters rise and fall with the floor thing->z = thing->floorz; }   ... A call to  is made to ascertain that floor, which is where a thing intersecting a ledge will be detected. The ledge is placed in the variable which then becomes the new associated floor of that thing.

This floor-finding step before updating the height ensures that things can not clip through walls around a descending lift, however the same mechanic is also what causes things below ledges to snap upwards on and around moving sectors.

Of note is that blockmap tiles are rather large and can easily contain things that would never touch the sector, and single sectors consisting of unconnected parts as well as concave sectors still have only one bounding box, potentially making them redundantly update things in an enormous and irrelevant area. The blockmap is also not static, having an origin point on the southwest corner of the map. This means that adding map geometry in the southwest part of a map may remove or add instances of this bug elsewhere by shifting the origin point of the blockmap.